#!/bin/bash

# Copyright (c) 2011-2024 Columbia University, System Level Design Group
# SPDX-License-Identifier: Apache-2.0

set -e

### Default
NAME_DEFAULT="dummy"
ESP_ROOT_DEFAULT=${PWD}
ID_DEFAULT="04A"
DATA_IN_SIZE_DEFAULT=1024
DATA_OUT_SIZE_DEFAULT=32

### Helpers
COL_RED='\033[0;31m'
COL_GREEN='\033[0;32m'
COL_CYAN='\033[0;96m'
COL_LBLUE='\033[0;44m'
COL_NORMAL='\033[0;39m'

bold=$(tput bold; echo -e $COL_CYAN)
normal=$(tput sgr0; echo -e $COL_NORMAL)
def=$(tput bold; echo -e $COL_CYAN)

die () {
    tput bold
    echo -e -n $COL_RED
    echo -n "  Error:"
    echo "${normal} ${1}"
    exit
}

warn () {
    tput bold
    echo -e -n $COL_RED
    echo -n "  Warning:"
    echo "${normal} ${1}"
}

yes_no () {
    read -p "${1} ${def}[N]${normal}: " SELECT
    SELECT=${SELECT:-N}
    case $SELECT in
	[Yy]* ) echo "y";;
	[Nn]* ) echo "n";;
	* ) echo "n";;
    esac
}

is_integer () {
    re='^[1-9][0-9]*$'
    if ! [[ $1 =~ $re ]] ; then
	echo "N"
    else
	echo "Y"
    fi
}

round_up() {
    x=$1
    y=$2
    echo $(( ((x-1) | (y-1)) + 1 ))
}

declare -A values
declare -A maxs

first_key () {
    for key in ${!values[@]}; do echo $key; break; done
}

keys_to_max () {
    for key in ${!maxs[@]}; do eval "$key"=${maxs[$key]}; done
}

keys_to_default () {
    for key in ${!maxs[@]}; do eval "$key"=${values[$key]}; done
}

echo "=== Initializing ESP accelerator template ==="
echo ""

### Accelerator name and design flow
read -p "  * Enter accelerator name ${def}[${NAME_DEFAULT}]${normal}: " NAME
NAME=${NAME:-$NAME_DEFAULT}

read -p "  * Select design flow (${bold}S${normal}tratus HLS, ${bold}V${normal}ivado HLS, ${bold}h${normal}ls4ml, ${bold}C${normal}atapult HLS, ${bold}R${normal}TL) ${def}[S]${normal}: " FLOW_SELECT
FLOW_SELECT=${FLOW_SELECT:-S}
case $FLOW_SELECT in
    [Ss]* ) FLOW="stratus_hls" FLOWSUFFIX="stratus";;
    [Vv]* ) FLOW="vivado_hls" FLOWSUFFIX="vivado";;
    [Hh]* ) FLOW="hls4ml" FLOWSUFFIX="hls4ml";;
    [Cc]* ) FLOW="catapult_hls" FLOWLANG="sysc" FLOWSUFFIX="catapult";;
    [Rr]* ) FLOW="rtl" FLOWSUFFIX="rtl";;
    * ) FLOW="stratus_hls" FLOWSUFFIX="stratus";;
esac

if [ $FLOW != "catapult_hls" ]; then
    NAMEFULL="$NAME"
    NAMEFULL+="_"
    NAMEFULL+="$FLOWSUFFIX"
else
    NAMEFULL="$NAME"
    NAMEFULL+="_"
    NAMEFULL+="$FLOWLANG"
    NAMEFULL+="_"
    NAMEFULL+="$FLOWSUFFIX"
fi

read -p "  * Enter ESP path ${def}[${ESP_ROOT_DEFAULT}]${normal}: " ESP_ROOT
ESP_ROOT=${ESP_ROOT:-$ESP_ROOT_DEFAULT}

if  ! test -e ${ESP_ROOT}/accelerators/$FLOW; then
    die "${ESP_ROOT}/accelerators/$FLOW not found; ESP_ROOT not pointing to the ESP repository"
fi
if  test -e ${ESP_ROOT}/accelerators/stratus_hls/$NAMEFULL; then
    die "accelerator ${NAMEFULL} already defined"
fi
if  test -e ${ESP_ROOT}/accelerators/vivado_hls/$NAMEFULL; then
    die "accelerator ${NAMEFULL} already defined"
fi
if  test -e ${ESP_ROOT}/accelerators/hls4ml/$NAMEFULL; then
    die "accelerator ${NAMEFULL} already defined"
fi
if  test -e ${ESP_ROOT}/accelerators/catapult_hls/$NAMEFULL; then
    die "accelerator ${NAMEFULL} already defined"
fi
if  test -e ${ESP_ROOT}/accelerators/rtl/$NAMEFULL; then
    die "accelerator ${NAMEFULL} already defined"
fi

## TODO adjust based on what hls4ml generates, paths names. Give a path example...
### Copy hls4ml project folder
if [ $FLOW == "hls4ml" ]; then
    read -p "  * Enter the path of the project folder generated by hls4ml: " HLS4ML_PRJ_PATH
    if [[ "${HLS4ML_PRJ_PATH}" != "" || "${HLS4ML_PRJ_PATH}" != "." ]]; then
	if ! test -e ${HLS4ML_PRJ_PATH}; then
	    die "The specified path doesn't exist. PATH=${HLS4ML_PRJ_PATH}"
	fi
    else
	die "The specified path is not valid. PATH=${HLS4ML_PRJ_PATH}"
    fi
fi

### Basic configuration
read -p "  * Enter unique accelerator id as three hex digits ${def}[${ID_DEFAULT}]${normal}: " ID
ID=${ID:-$ID_DEFAULT}
ID=$(echo $ID | awk '{print tolower($0)}')


NPARAMS=0

### Configuration registers
if [ $FLOW != "hls4ml" ]; then
    echo "  * Enter accelerator registers"
    read -p "    - register $NPARAMS name ${def}[size]${normal}: " param
    param=${param:-"size"}
    while [ "${param}" != "" ]; do
	read -p "    - register $NPARAMS default value ${def}[1]${normal}: " val
	val=${val:-1}
	read -p "    - register $NPARAMS max value ${def}[${val}]${normal}: " max
	max=${max:-${val}}

	values+=( ["$param"]=$val )
	maxs+=( ["$param"]=$max )

	NPARAMS=$((NPARAMS+1))

	if [ $NPARAMS == 48 ]; then
	    echo "    # Cannot specify more than 48 configuration registers"
	    break;
	fi

	read -p "    - register $NPARAMS name ${def}[]${normal}: " param

    done
fi


first_param=$(first_key)
keys_to_max

### Input / Output and local memory
echo "  * Configure PLM size and create skeleton for load and store:"
read -p "    - Enter data bit-width (8, 16, 32, 64) ${def}[32]${normal}: " data_width
data_width=${data_width:-32}
while (true); do
    case $data_width in
	8 ) hsize="SIZE_BYTE"; break;;
	16 ) hsize="SIZE_HWORD"; break;;
	32 ) hsize="SIZE_WORD"; break;;
	64 ) hsize="SIZE_DWORD"; break;;
	* ) read -p "      Please enter a valid bit-width (8, 16, 32, 64) ${def}[32]${normal}: " data_width; data_width=${data_width:-32};;
    esac
done
dma_adj=$(( 8 / (data_width/8) ))

if [ $FLOW == "hls4ml" ]; then
    read -p "    - Enter the number of fixed point fractional bits ${def}[16]${normal}: " frac_bits
    frac_bits=${frac_bits:-16}
    while (true); do
	if [ $frac_bits -ge $data_width ]; then
	    read -p "      Please enter a valid number of fractional bits ${def}[16]${normal}: " frac_bits; frac_bits=${frac_bits:-16}
	else
	    break
	fi
    done
    int_bits=$((data_width-frac_bits))
fi

# TODO: add checks on following user input
set -f
while true; do
    if [ $FLOW == "hls4ml" ]; then
	read -p "    - Enter input data size (number of words) ${def}[1024]${normal}: " data_in_size_expr
	data_in_size_expr=${data_in_size_expr:-$DATA_IN_SIZE_DEFAULT}
    else
	read -p "    - Enter input data size in terms of configuration registers (e.g. 2 * $first_param}) ${def}[$first_param]${normal}: " data_in_size_expr
	if [ "$data_in_size_expr" == "" ]; then data_in_size_expr="$first_param"; fi
    fi
    data_in_size_max=$((data_in_size_expr))
    if [ "$(is_integer $data_in_size_max)" == "Y" ]; then
	data_in_size_max=$(round_up $data_in_size_max $dma_adj) # Make size aligned to 64-bit to prevent non-aligend offsets
	echo "      data_in_size_max = ${data_in_size_max}"; break
    else
	warn "invalid expression \"${data_in_size_expr}\""
    fi
done

while true; do
    if [ $FLOW == "hls4ml" ]; then
	read -p "    - Enter output data size (number of words) ${def}[32]${normal}: " data_out_size_expr
	data_out_size_expr=${data_out_size_expr:-$DATA_OUT_SIZE_DEFAULT}
    else
	read -p "    - Enter output data size in terms of configuration registers (e.g. 2 * $first_param) ${def}[$first_param]${normal}: " data_out_size_expr
	if [ "$data_out_size_expr" == "" ]; then data_out_size_expr="$first_param"; fi
    fi
    data_out_size_max=$((data_out_size_expr))
    if [ "$(is_integer $data_out_size_max)" == "Y" ]; then
	data_out_size_max=$(round_up $data_out_size_max $dma_adj) # Make size aligned to 64-bit to prevent non-aligend offsets
	echo "      data_out_size_max = ${data_out_size_max}"; break
    else
	warn "invalid expression \"${data_out_size_expr}\""
    fi
done

if [ $FLOW != "hls4ml" ]; then
    read -p "    - Enter an integer chunking factor (use 1 if you want PLM size equal to data size) ${def}[1]${normal}: " chunking_factor
    chunking_factor=${chunking_factor:-1}

    in_word=$(( (data_in_size_max+chunking_factor-1)/chunking_factor ))
    out_word=$(( (data_out_size_max+chunking_factor-1)/chunking_factor ))
    in_word=$(round_up $in_word $dma_adj)
    out_word=$(round_up $out_word $dma_adj)
    echo "      Input PLM has ${in_word} ${data_width}-bits words"
    echo "      Output PLM has ${out_word} ${data_width}-bits words"
fi

while true; do
    if [ $FLOW != "hls4ml" ]; then
	read -p "    - Enter number of input data to be processed in batch (can be function of configuration registers) ${def}[1]${normal}: " batching_factor_expr
    else
	read -p "    - Enter number of inputs that the hls4ml testbench has (tb_data/tb_input_features.dat) ${def}[1]${normal}: " batching_factor_expr
	values+=( ["nbursts"]=$batching_factor_expr )
    fi
    if [ "$batching_factor_expr" == "" ]; then batching_factor_expr="1"; fi
    batching_factor_max=$((batching_factor_expr))
    if [ "$(is_integer $batching_factor_max)" == "Y" ]; then
	echo "      batching_factor_max = ${batching_factor_max}"; break
    else
	warn "invalid expression \"${batching_factor_expr}\""
    fi
done

if [[ $data_in_size_max == $data_out_size_max && $FLOW != "hls4ml" ]]; then
    IN_PLACE=$(yes_no "    - Is output stored in place?")
else
    IN_PLACE="n"
fi
set +f

# Memory footprint in MB
if [ "$IN_PLACE" == "y" ]; then
    memory_words=$(( batching_factor_max * data_in_size_max ))
else
    memory_words=$(( batching_factor_max * (data_in_size_max + data_out_size_max) ))
fi
memory_footprint=$(( memory_words * (data_width/8) ))
TLB_ENTRIES=$(( (memory_footprint + 1048575) / 1048576 ))
if (( $TLB_ENTRIES < 4 )); then TLB_ENTRIES=4; fi;

### Generate accelerator skeleton
CURR_DIR=${PWD}
cd $ESP_ROOT

FLOW_DIR=$FLOW

LOWER=$(echo $NAME | awk '{print tolower($0)}')
UPPER=$(echo $LOWER | awk '{print toupper($0)}')
LOWERFULL=$(echo $NAMEFULL | awk '{print tolower($0)}')
UPPERFULL=$(echo $LOWERFULL | awk '{print toupper($0)}')

ACC_DIR=$ESP_ROOT/accelerators/$FLOW_DIR/$LOWERFULL

TEMPLATES_DIR=$ESP_ROOT/tools/accgen/templates/$FLOW

mkdir -p $ACC_DIR/hw

if [ "$FLOW" == "stratus_hls" ]; then

    dirs="src  hls  tb"

elif [ "$FLOW" == "catapult_hls" ]; then

    dirs="src  hls  tb  inc  sim"

elif [ "$FLOW" == "vivado_hls" ]; then

    dirs="src  inc  hls  tb"

elif [ "$FLOW" == "hls4ml" ]; then

    dirs="src  inc  hls  tb"

elif [ "$FLOW" == "rtl" ]; then
    dirs="src hls"
fi

## initialize all design folders
for d in $dirs; do
    mkdir -p $ACC_DIR/hw/$d
    cd $ACC_DIR/hw/$d
    if ! [[ "$FLOW" == "rtl" && "$d" == "hls" ]]; then
	cp -r $TEMPLATES_DIR/$d/* .
    fi

    if cat /etc/os-release | grep -q -i ubuntu; then
        rename "s/accelerator/$LOWER/g" *
	rename "s/acc_full/$LOWERFULL/g" *
        rename "s/accelerator/$LOWER/g" */*
	rename "s/acc_full/$LOWERFULL/g" */*
    elif cat /etc/os-release | grep -q -i centos; then
        rename accelerator $LOWER *
	rename acc_full $LOWERFULL *
        rename accelerator $LOWER */*
	rename acc_full $LOWERFULL */*
    elif cat /etc/os-release | grep -q -i rhel; then
        rename accelerator $LOWER *
	rename acc_full $LOWERFULL *
        rename accelerator $LOWER */*
	rename acc_full $LOWERFULL */*
    fi
    
    if [[ "$FLOW" == "rtl" && "$d" != "hls" ]]; then
	sed -i "s/<accelerator_name>/$LOWER/g" */*
	sed -i "s/<ACCELERATOR_NAME>/$UPPER/g" */*
	sed -i "s/<acc_full_name>/$LOWERFULL/g" */*
	sed -i "s/<ACC_FULL_NAME>/$UPPERFULL/g" */*
    elif [ "$FLOW" != "rtl" ]; then
	find . -type f -exec sed -i "s/<accelerator_name>/$LOWER/g" {} +
	find . -type f -exec sed -i "s/<ACCELERATOR_NAME>/$UPPER/g" {} +
	find . -type f -exec sed -i "s/<acc_full_name>/$LOWERFULL/g" {} +
	find . -type f -exec sed -i "s/<ACC_FULL_NAME>/$UPPERFULL/g" {} +
    fi


    if [[ "$FLOW" == "stratus_hls" && "$d" == "hls" ]]; then
	ln -s ../../../common/hls/Makefile
    fi
    if [[ "$FLOW" == "vivado_hls" && "$d" == "hls" ]]; then
	ln -s ../../../common/hls/Makefile
	ln -s ../../../common/hls/common.tcl
    fi
    if [[ "$FLOW" == "hls4ml" && "$d" == "hls" ]]; then
	ln -s ../../../common/hls/Makefile
	ln -s ../../../common/hls/common.tcl
    fi
    if [[ "$FLOW" == "rtl" && "$d" == "hls" ]]; then
	ln -s ../../../common/hls/Makefile
    fi
done


if [ "$FLOW" == "stratus_hls" ]; then
    ## Initialize SystemC execution folder (no HLS license required)
    mkdir -p $ACC_DIR/hw/sim
    cd $ACC_DIR/hw/sim
    echo "include ../../../common/systemc.mk" > Makefile
    echo "$LOWER" > .gitignore
fi

if [ "$FLOW" == "vivado_hls" ]; then
    ## Initialize gitignore
    cd $ACC_DIR/hw
    echo "$LOWER" > .gitignore
    echo "*.log" >> .gitignore
fi

if [ "$FLOW" == "hls4ml" ]; then
    ## Initialize gitignore
    cd $ACC_DIR/hw
    echo "$LOWER" > .gitignore
    echo "*.log" >> .gitignore
    echo "  * Copying hls4ml project folder ($HLS4ML_PRJ_PATH) to accelerator directory ($ACC_DIR/hw). Renaming it 'hls4ml'."
    cp -r $HLS4ML_PRJ_PATH $ACC_DIR/hw/hls4ml
fi

if [ "$FLOW" != "catapult_hls" ]; then
    XML_NAME=$LOWER
else
    XML_NAME="$LOWER"
    XML_NAME+="_"
    XML_NAME+="$FLOWLANG"
fi

if [ "$FLOW" != "catapult_hls" ]; then
    FLOW_NAME="${FLOW}"
else
    FLOW_NAME="${FLOW}_${FLOWLANG}"
fi
## initialize xml file
cd $ACC_DIR/hw
echo '<?xml version="1.0" encoding="UTF-8"?>' > $XML_NAME.xml
echo '<sld>' >> $XML_NAME.xml
echo -n  "  <accelerator name=\"${XML_NAME}\" " >> $XML_NAME.xml
echo -n                 "desc=\"Accelerator ${UPPER}\" " >> $XML_NAME.xml
echo -n                 "data_size=\"${TLB_ENTRIES}\" " >> $XML_NAME.xml
echo -n                 "device_id=\"${ID}\" " >> $XML_NAME.xml
echo                    "hls_tool=\"${FLOW_NAME}\">" >> $XML_NAME.xml
for key in ${!values[@]}; do
    echo -n  "    <param name=\"${key}\" " >> $XML_NAME.xml
    echo -n             "desc=\"${key}\" " >> $XML_NAME.xml
    echo "/>" >> $XML_NAME.xml
done
echo '  </accelerator>' >> $XML_NAME.xml
echo '</sld>' >> $XML_NAME.xml


## customize configuration registers
indent="\ \ \ \ \ \ \ \ "
if [ "$FLOW" == "stratus_hls" ]; then
    cd $ACC_DIR/hw/src
    for key in ${!values[@]}; do
	if [ $key == $(first_key) ]; then sep=""; else sep=", "; fi
	sed -i "/\/\* <<--ctor-->> \*\//a ${indent}this->${key} = ${values[$key]};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--ctor-args-->> \*\//a ${indent}int32_t ${key}${sep}" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--ctor-custom-->> \*\//a ${indent}this->${key} = ${key};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--eq-->> \*\//a ${indent}if (${key} != rhs.${key}) return false;" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--assign-->> \*\//a ${indent}${key} = other.${key};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--print-->> \*\//a ${indent}os << \"${key}\ = \" << conf_info.${key} << \"${sep}\";" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--params-->> \*\//a ${indent}int32_t ${key};" ${LOWER}_conf_info.hpp
    done
fi

indent="\ \ \ "
if [ "$FLOW" == "rtl" ]; then
    cd $ACC_DIR/hw/src
    sep=","
    for key in ${!values[@]}; do
	sed -i "/\/\* <<--params-list-->> \*\//a conf_info_${key}${sep}" ${LOWERFULL}_basic_dma32/${LOWERFULL}_basic_dma32.v
	sed -i "/\/\* <<--params-list-->> \*\//a conf_info_${key}${sep}" ${LOWERFULL}_basic_dma64/${LOWERFULL}_basic_dma64.v
	sed -i "/\/\* <<--params-def-->> \*\//a ${indent}input [31:0]  conf_info_${key};" ${LOWERFULL}_basic_dma32/${LOWERFULL}_basic_dma32.v
	sed -i "/\/\* <<--params-def-->> \*\//a ${indent}input [31:0]  conf_info_${key};" ${LOWERFULL}_basic_dma64/${LOWERFULL}_basic_dma64.v
    done
fi
indent="\ \ \ \ \ \ \ \ "

if [ "$FLOW" == "catapult_hls" ]; then
    cd $ACC_DIR/hw/inc
    sed -i "s/\/\* <<--nparam-->> \*\//$NPARAMS/g" ${LOWER}_conf_info.hpp
    for key in ${!values[@]}; do
	if [ $key == $(first_key) ]; then sep=""; else sep=", "; fi
	sed -i "/\/\* <<--marsh-->> \*\//a ${indent}m &${key};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--ctor-->> \*\//a ${indent}this->${key} = ${values[$key]};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--ctor-args-->> \*\//a ${indent}int32_t ${key}${sep}" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--ctor-custom-->> \*\//a ${indent}this->${key} = ${key};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--sctrc-->> \*\//a ${indent}sc_trace(tf,v.${key}, NAME + \".${key}\");" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--eq-->> \*\//a ${indent}if (${key} != rhs.${key}) return false;" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--assign-->> \*\//a ${indent}${key} = other.${key};" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--print-->> \*\//a ${indent}os << \"${key}\ = \" << conf_info.${key} << \"${sep}\";" ${LOWER}_conf_info.hpp
	sed -i "/\/\* <<--params-->> \*\//a ${indent}int32_t ${key};" ${LOWER}_conf_info.hpp
    done
fi


## PLM memories for both 32-bit and 64-bit NoC
dma_width=(32 64)
if [ "$FLOW" == "stratus_hls" ]; then

    # accelerator.hpp
    cd $ACC_DIR/hw/src
    indent="\ \ \ \ \ \ \ \ "
    sed -i "/\/\* <<--defines-->> \*\//a #define PLM_IN_WORD $in_word" ${LOWER}.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define PLM_OUT_WORD $out_word" ${LOWER}.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define DMA_SIZE $hsize" ${LOWER}.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define DATA_WIDTH $data_width" ${LOWER}.hpp

    sed -i "/\/\* <<--plm-bind-->> \*\//a ${indent}HLS_MAP_plm(plm_in_ping, PLM_IN_NAME);" ${LOWER}.hpp
    sed -i "/\/\* <<--plm-bind-->> \*\//a ${indent}HLS_MAP_plm(plm_in_pong, PLM_IN_NAME);" ${LOWER}.hpp
    sed -i "/\/\* <<--plm-bind-->> \*\//a ${indent}HLS_MAP_plm(plm_out_ping, PLM_OUT_NAME);" ${LOWER}.hpp
    sed -i "/\/\* <<--plm-bind-->> \*\//a ${indent}HLS_MAP_plm(plm_out_pong, PLM_OUT_NAME);" ${LOWER}.hpp

    for d in ${dma_width[@]}; do
	p=$(( (d+data_width-1)/data_width ))

	# memlist.txt
	cd $ACC_DIR/hw
	plm_in_name=${LOWER}_plm_block_in_dma${d}
	plm_out_name=${LOWER}_plm_block_out_dma${d}
	echo "${plm_in_name} $in_word $data_width ${p}w:0r 0w:1r" >> memlist.txt
	echo "${plm_out_name} $out_word $data_width 1w:0r 0w:${p}r" >> memlist.txt

	# accelerator_directives.hpp
	cd $ACC_DIR/hw/src
	dbpw=$(( (data_width+d-1)/d ))
	dwpb=$(( d/data_width )) # Zero on 32-bit DMA and 64-bit word
	sed -i "s/\/\* <<--dbpw${d}-->> \*\//${dbpw}/g" ${LOWER}_directives.hpp
	sed -i "s/\/\* <<--dwpb${d}-->> \*\//${dwpb}/g" ${LOWER}_directives.hpp
	sed -i "s/\/\* <<--plm_in_name${d}-->> \*\//\"${plm_in_name}\"/g" ${LOWER}_directives.hpp
	sed -i "s/\/\* <<--plm_out_name${d}-->> \*\//\"${plm_out_name}\"/g" ${LOWER}_directives.hpp
    done

fi


if [ "$FLOW" == "catapult_hls" ]; then
    # accelerator.hpp
    cd $ACC_DIR/hw/inc
    indent="\ \ \ \ \ \ \ \ "
    sed -i "/\/\* <<--defines-->> \*\//a #define PLM_IN_WORD $in_word" ${LOWER}_specs.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define PLM_OUT_WORD $out_word" ${LOWER}_specs.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define DMA_SIZE $hsize" ${LOWER}_specs.hpp
    sed -i "/\/\* <<--defines-->> \*\//a #define DATA_WIDTH $data_width" ${LOWER}_specs.hpp
    sed -i "s/\/\* <<--mem-footprint-->> \*\//${memory_footprint}/g" ${LOWER}_specs.hpp


    for d in ${dma_width[@]}; do
	p=$(( (d+data_width-1)/data_width ))
	dbpw=$(( (data_width+d-1)/d ))
	dwpb=$(( d/data_width ))
	sed -i "s/\/\* <<--dbpw${d}-->> \*\//${dbpw}/g" ${LOWER}_specs.hpp
	sed -i "s/\/\* <<--dwpb${d}-->> \*\//${dwpb}/g " ${LOWER}_specs.hpp
	sed -i "s/\/\* <<--inwp${d}-->> \*\//${p}/g" ${LOWER}_specs.hpp
    done

fi

if [ "$FLOW" == "vivado_hls" ]; then

    # espacc_config.h
    cd $ACC_DIR/hw/inc
    sed -i "s/\/\* <<--plm-in-word-->> \*\//$in_word/g" espacc_config.h
    sed -i "s/\/\* <<--plm-out-word-->> \*\//$out_word/g" espacc_config.h

    # espacc.h espacc.cc tb.cc
    indent="\	\ "
    cd $ACC_DIR/hw
    for key in ${!values[@]}; do
	for f in "inc/espacc.h src/espacc.cc"; do
	    sed -i "/\/\* <<--params-->> \*\//a ${indent}const unsigned conf_info_${key}," ${f}
	done
	sed -i "/\/\* <<--params-->> \*\//a ${indent}const unsigned ${key} = ${values[$key]};" tb/tb.cc
	sed -i "/\/\* <<--local-params-->> \*\//a ${indent}const unsigned ${key} = conf_info_${key};" src/espacc.cc
	sed -i "/\/\* <<--compute-params-->> \*\//a ${indent}const unsigned ${key}," src/espacc.cc
	for f in "tb/tb.cc src/espacc.cc"; do
	    sed -i "/\/\* <<--args-->> \*\//a ${indent}${indent}${key}," ${f}
	done
    done

    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" src/espacc.cc
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//store_offset/g" src/espacc.cc
    fi
    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" tb/tb.cc
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//dma_in_size/g" tb/tb.cc
    fi
    for f in "tb/tb.cc src/espacc.cc"; do
	sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" ${f}
	sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" ${f}
	sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" ${f}
	sed -i "s/\/\* <<--chunking-factor-->> \*\//${chunking_factor}/g" ${f}
    done

    # hls/custom.tcl hls/directives.tcl
    cd $ACC_DIR/hw
    if [ $data_width == 64 ]; then
	dma_allowed="64"
    else
	dma_allowed="32 64"
    fi
    sed -i "s/<<--dma-width-->>/${dma_allowed}/g" hls/custom.tcl
    sed -i "s/<<--data-widths-->>/${data_width}/g" hls/custom.tcl
    for key in ${!values[@]}; do
	sed -i "/<<--directives-param-->>/a set_directive_interface -mode ap_none \"top\" conf_info_${key}" hls/directives.tcl
    done
fi

if [ "$FLOW" == "hls4ml" ]; then

    cd $ACC_DIR/hw

    # espacc_config.h
    sed -i "s/\/\* <<--frac-bits-->> \*\//$frac_bits/g" inc/espacc_config.h
    sed -i "s/\/\* <<--plm-in-word-->> \*\//$data_in_size_expr/g" inc/espacc_config.h
    sed -i "s/\/\* <<--plm-out-word-->> \*\//$data_out_size_expr/g" inc/espacc_config.h

    # tb.cc
    sed -i "s/\/\* <<--tb-inputs-->> \*\//$batching_factor_expr/g" tb/tb.cc

    # hls/custom.tcl
    if [ $data_width == 64 ]; then
	dma_allowed="64"
    else
	dma_allowed="32 64"
    fi
    sed -i "s/<<--dma-width-->>/${dma_allowed}/g" hls/custom.tcl
    sed -i "s/<<--data-widths-->>/${data_width}/g" hls/custom.tcl
fi

## Load/Store
if [ "$FLOW" == "stratus_hls" ]; then

    # accelerator.cpp
    cd $ACC_DIR/hw/src
    for key in ${!values[@]}; do
	indent="\ \ \ \ "
	sed -i "/\/\* <<--params-->> \*\//a ${indent}int32_t ${key};" ${LOWER}.cpp
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--local-params-->> \*\//a ${indent}${key} = config.${key};" ${LOWER}.cpp
    done
    sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" ${LOWER}.cpp
    sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" ${LOWER}.cpp
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" ${LOWER}.cpp
    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" ${LOWER}.cpp
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//store_offset/g" ${LOWER}.cpp
    fi
fi

if [ "$FLOW" == "catapult_hls" ]; then
    # accelerator.cpp
    cd $ACC_DIR/hw/src
    for key in ${!values[@]}; do
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--local-params-->> \*\//a ${indent}uint32_t ${key} = conf.${key};" ${LOWER}.cpp
    done
    sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" ${LOWER}.cpp
    sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" ${LOWER}.cpp
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" ${LOWER}.cpp
    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" ${LOWER}.cpp
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//store_offset/g" ${LOWER}.cpp
    fi
fi


### Generate skeleton for unit testbench
if [ "$FLOW" == "stratus_hls" ]; then

    # system.hpp
    cd $ACC_DIR/hw/tb
    sed -i "s/\/\* <<--mem-footprint-->> \*\//${memory_footprint}/g" system.hpp
    sed -i "s/\/\* <<--data-width-->> \*\//${data_width}/g" system.hpp
    for key in ${!values[@]}; do
	indent="\ \ \ \ "
	sed -i "/\/\* <<--params-->> \*\//a ${indent}int32_t ${key};" system.hpp
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--params-default-->> \*\//a ${indent}${key} = ${values[$key]};" system.hpp
    done

    # system.cpp
    sed -i "s/\/\* <<--data-width-->> \*\//${data_width}/g" system.cpp
    sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" system.cpp
    sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" system.cpp
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" system.cpp
    for key in ${!values[@]}; do
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--params-->> \*\//a ${indent}config.${key} = ${key};" system.cpp
    done
    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" system.cpp
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//in_size/g" system.cpp
    fi
fi

### Generate skeleton for unit testbench
if [ "$FLOW" == "catapult_hls" ]; then
    cd $ACC_DIR/hw/tb
    # testbench.hpp
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" testbench.hpp
    for key in ${!values[@]}; do
	indent="\ \ \ \ "
	sed -i "/\/\* <<--params-->> \*\//a ${indent}uint32_t ${key};" testbench.hpp
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--params-default-->> \*\//a ${indent}${key} = ${values[$key]};" testbench.hpp
    done
    # testbench.cpp
    sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" testbench.cpp
    sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" testbench.cpp
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" testbench.cpp
    for key in ${!values[@]}; do
	indent="\ \ \ \ \ \ \ \ "
	sed -i "/\/\* <<--params-->> \*\//a ${indent}config.${key} = ${key};" testbench.cpp
    done
fi


### Device driver folders initialization
if [ $FLOW == "hls4ml" ]; then
    TEMPLATES_DIR=$ESP_ROOT/tools/accgen/templates/drivers_hls4ml
else
    TEMPLATES_DIR=$ESP_ROOT/tools/accgen/templates/drivers
fi
SOFT_DIR=$ACC_DIR/sw
dirs="baremetal linux/app linux/driver linux/include"

## initialize all driver folders
for d in $dirs; do
    new_dir=$SOFT_DIR/$d
    mkdir -p $new_dir
    cd $new_dir
    cp $TEMPLATES_DIR/$d/* .

    if cat /etc/os-release | grep -q -i ubuntu; then
        rename "s/accelerator/$LOWER/g" *
	rename "s/acc_full/$LOWERFULL/g" *
    elif cat /etc/os-release | grep -q -i centos; then
	rename accelerator $LOWER *
	rename acc_full $LOWERFULL *
    elif cat /etc/os-release | grep -q -i rhel; then
	rename accelerator $LOWER *
	rename acc_full $LOWERFULL *
    fi

    sed -i "s/<accelerator_name>/$LOWER/g" *
    sed -i "s/<ACCELERATOR_NAME>/$UPPER/g" *
    sed -i "s/<acc_full_name>/$LOWERFULL/g" *
    sed -i "s/<ACC_FULL_NAME>/$UPPERFULL/g" *
done

## Linux driver include file
cd $SOFT_DIR/linux/include
indent="\	"
for key in ${!values[@]}; do
    sed -i "/\/\* <<--regs-->> \*\//a ${indent}unsigned ${key};" ${LOWERFULL}.h
done

## Linux and baremetal drivers
cd $SOFT_DIR
indent="\	"
sed -i "s/\/\* <<--id-->> \*\//${ID}/g" linux/driver/${LOWERFULL}.c

user_reg_offset=64
for key in ${!values[@]}; do
    key_upper=$(echo $key | awk '{print toupper($0)}')
    reg_offset_hex=$(printf '%x\n' ${user_reg_offset})
    register_name="${UPPER}_${key_upper}_REG"

    for f in "linux/driver/${LOWERFULL}.c baremetal/${LOWER}.c"; do
	sed -i "/\/\* <<--regs-->> \*\//a #define ${register_name} 0x${reg_offset_hex}" ${f}
    done
    sed -i "/\/\* <<--regs-config-->> \*\//a ${indent}iowrite32be(a->${key}, esp->iomem + ${register_name});" linux/driver/${LOWERFULL}.c
    sed -i "/\/\* <<--regs-config-->> \*\//a ${indent}${indent}iowrite32(dev, ${register_name}, ${key});" baremetal/${LOWER}.c
    user_reg_offset=$((user_reg_offset + 4))
done

for f in "baremetal/${LOWER}.c linux/app/cfg.h"; do
    sed -i "s/\/\* <<--token-type-->> \*\//int${data_width}_t/g" ${f}
done

sed -i "s/\/\* <<--id-->> \*\//0x${ID}/g" baremetal/${LOWER}.c

for f in "baremetal/${LOWER}.c linux/app/${LOWER}.c linux/app/cfg.h"; do
    sed -i "s/\/\* <<--number of transfers-->> \*\//${batching_factor_expr}/g" ${f}
    sed -i "s/\/\* <<--data_in_size-->> \*\//${data_in_size_expr}/g" ${f}
    sed -i "s/\/\* <<--data_out_size-->> \*\//${data_out_size_expr}/g" ${f}
    if [ "$IN_PLACE" == "y" ]; then
	sed -i "s/\/\* <<--store-offset-->> \*\//0/g" ${f}
    else
	sed -i "s/\/\* <<--store-offset-->> \*\//in_len/g" ${f}
    fi
done

for key in ${!values[@]}; do
    key_upper=$(echo $key | awk '{print toupper($0)}')
    sed -i "/\/\* <<--params-def-->> \*\//a #define ${key_upper} ${values[$key]}" linux/app/cfg.h
    sed -i "/\/\* <<--params-->> \*\//a const int32_t ${key} = ${key_upper};" linux/app/cfg.h
    sed -i "/\/\* <<--params-->> \*\//a const int32_t ${key} = ${values[$key]};" baremetal/${LOWER}.c
    sed -i "s/\/\* <<--int_bits-->> \*\//$int_bits/g" linux/app/cfg.h
    sed -i "s/\/\* <<--int_bits-->> \*\//$int_bits/g" baremetal/${LOWER}.c
    sed -i "s/\/\* <<--data_width-->> \*\//$data_width/g" linux/app/cfg.h
    sed -i "s/\/\* <<--data_width-->> \*\//$data_width/g" baremetal/${LOWER}.c
    sed -i "/\/\* <<--print-params-->> \*\//a ${indent}printf(\"  .${key} = %d\\\n\", ${key});" linux/app/${LOWER}.c
    sed -i "/\/\* <<--descriptor-->> \*\//a ${indent}${indent}.${key} = ${key_upper}," linux/app/cfg.h
done

## Prepare input and golden output files
if [ $FLOW == "hls4ml" ]; then
    if test -e $ACC_DIR/hw/hls4ml/tb_data/tb_output_predictions.dat; then
	if test -e $ACC_DIR/hw/hls4ml/tb_data/csim_results.log; then
	    mv $ACC_DIR/hw/hls4ml/tb_data/tb_output_predictions.dat \
	       $ACC_DIR/hw/hls4ml/tb_data/tb_output_predictions_float.dat
	    mv $ACC_DIR/hw/hls4ml/tb_data/csim_results.log \
	       $ACC_DIR/hw/hls4ml/tb_data/tb_output_predictions_fixed.dat

	    cd  $SOFT_DIR/linux/app
	    mkdir -p data
	    cp -r $ACC_DIR/hw/hls4ml/tb_data/* data

	    cd  $SOFT_DIR/baremetal
	    mkdir -p data
	    cat $ACC_DIR/hw/hls4ml/tb_data/tb_input_features.dat | tr " " "\n" > data/input.c
	    cat $ACC_DIR/hw/hls4ml/tb_data/tb_output_predictions_fixed.dat | \
		tr " " "\n" > data/output_gold.c
	    sed -e '/^$/d' -i data/input.c
	    sed -e '/^$/d' -i data/output_gold.c
	    sed -e 's/^/in[i++] = fl2fx(/' -i data/input.c
	    sed -e 's/^/gold[i++] = fl2fx(/' -i data/output_gold.c
	    sed -e 's/$/);/' -i data/input.c
	    sed -e 's/$/);/' -i data/output_gold.c
	fi
    fi
fi

echo ""
echo "=== Generated accelerator skeleton for $NAME ==="


cd $CURR_DIR
